////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   ParticleEffect.h
//  Version:     v1.00
//  Created:     10/7/2003 by Timur.
//  Compilers:   Visual Studio.NET
//  Description: 
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#ifndef __particleeffect_h__
#define __particleeffect_h__
#pragma once

#include "IParticles.h"
#include "Cry3DEngineBase.h"
#include "ParticleParams.h"
#include "CryPtrArray.h"
#include "ICryMiniGUI.h"

#define fHUGE			1e9f		// Use for practically infinite values, to simplify comparisons.
													// 1e9 s > 30 years, 1e9 m > earth-moon distance.
													// Convertible to int32.

// Convert certain particle parameters where zero denotes an essentially infinite value.
ILINE float ZeroIsHuge(float f)
{
	return if_pos_else(f, f, fHUGE);
}

//////////////////////////////////////////////////////////////////////////
// Set of flags that can have 3 states: forced on, forced off, or neutral.
template<class T>
struct TrinaryFlags
{
	T		On, Off;

	TrinaryFlags()
		: On(0), Off(0) {}

	void Clear()
		{ On = Off = 0; }

	// Set from an int where -1 is off, 0 is neutral, 1 is forced
	void SetState(int state, T flags)
	{
		On &= ~flags;
		Off &= ~flags;
		if (state < 0)
			Off |= flags;
		else if (state > 0)
			On |= flags;
	}

	T operator & (T flags) const
		{ return (flags | On) & ~Off; }

	T operator & (TrinaryFlags<T> const& other) const
		{ return (On | other.On) & ~(Off | other.Off); }
};

//////////////////////////////////////////////////////////////////////////
enum EEffectFlags
{
	// Physical phenomena affecting particles.
	EFF_LOADED				= BIT(0),

	// Environmental requirements.
	ENV_GRAVITY				= BIT(1),
	ENV_WIND					= BIT(2),
	ENV_WATER					= BIT(3),

	// Collision targets.
	ENV_TERRAIN				= BIT(4),
	ENV_STATIC_ENT		= BIT(5),
	ENV_DYNAMIC_ENT		= BIT(6),

	ENV_COLLIDE_ANY		= ENV_TERRAIN | ENV_STATIC_ENT | ENV_DYNAMIC_ENT,
	ENV_COLLIDE_PHYSICS	= ENV_STATIC_ENT | ENV_DYNAMIC_ENT,
	ENV_COLLIDE_CACHE	= ENV_TERRAIN | ENV_STATIC_ENT,

	// Additional effects.
	EFF_SOUND					= BIT(7),		// Produces sound.
	EFF_FORCE					= BIT(8),		// Produces physical force.

	EFF_ANY						= EFF_SOUND | EFF_FORCE,

	// Exclusive rendering methods.
	REN_SPRITE				= BIT(9),
	REN_GEOMETRY			= BIT(10),
	REN_DECAL					= BIT(11),
	REN_LIGHTS				= BIT(12),		// Adds light to scene.

	REN_ANY						= REN_SPRITE | REN_GEOMETRY | REN_DECAL | REN_LIGHTS,

	// Visual effects of particles
	REN_CAST_SHADOWS	= BIT(13),
	REN_BIND_CAMERA		= BIT(14),
	REN_REFRACTION		= BIT(15),		// Uses refraction shader.

	// General functionality
	REN_THREAD				= BIT(18),		// Can update in secondary thread.
	EFF_DYNAMIC_BOUNDS	= BIT(19)		// Requires updating particles every frame.
};

struct SExternForces
{
	Vec3	vAccel;
	Vec3	vWind;
	Plane	plWater;

	SExternForces()
	{}

	SExternForces( type_zero )
		: vAccel(ZERO), vWind(ZERO), plWater(Vec3(0,0,1), -WATER_LEVEL_UNKNOWN)
	{}

	void Add( SExternForces const& other, uint32 nEnvFlags )
	{
		if (nEnvFlags & ENV_GRAVITY)
			vAccel = other.vAccel;
		if (nEnvFlags & ENV_WIND)
			vWind += other.vWind;
		if (nEnvFlags & ENV_WATER)
			if (other.plWater.d < plWater.d)
				plWater = other.plWater;
	}
};

struct SLocalForces: SExternForces
{
	float	fDrag;
};

//
// Additional runtime parameters.
//
struct ResourceParticleParams: ParticleParams, Cry3DEngineBase
{
	PS3_ALIGNMENT_BARRIER(4)

	// Texture, material, geometry params.
	float									fTexAspect;								// H/V aspect ratio.
	uint16								nRenderElemFlags;					// RenderElement flags required.
	int16									nTexId;										// Texture id for sprite.
	_smart_ptr<IMaterial>	pMaterial;								// Used to override the material
	_smart_ptr<IStatObj>	pStatObj;									// If it isn't set to 0, this object will be used instead of a sprite
	CCryNameCRC						crcSoundName;							// Cached CRC value of the particle sound name
	uint32								nEnvFlags;								// Summary of environment info needed for effect.
	TrinaryFlags<uint32>	nRenObjFlags;							// Flags for renderer.

	ResourceParticleParams()
	{
		Init();
	}

	explicit ResourceParticleParams( const ParticleParams& params )
		: ParticleParams(params)
	{
		Init();
	}

	bool ResourcesLoaded() const
	{
		return nEnvFlags & EFF_LOADED;
	}
	void Correct( int nVersion, XmlNodeRef paramsNode );

	int LoadResources();
	void UnloadResources();
	void UpdateTextureAspect();

	// Shader params
	void SetTileInfo( float afInfo[4], uint32 nActiveObjFlags ) const;
	void SetMotionBlurInfo( float afInfo[3] ) const;

	void ComputeEnvironmentFlags();
	bool IsActive() const;

	void GetStaticBounds( AABB& bbResult, const QuatTS& loc, float fSpeedScale, const Vec3& vSpawnSize, float fAngMin, float fAngMax, bool bWithSize, float fMaxLife, const SExternForces& forces ) const;
	void GetTravelBounds( AABB& bbResult, float fTime, const QuatTS& loc, float fSpeedScale, float fAngMin, float fAngMax, const SExternForces& forces ) const;
	bool IsImmortal() const
	{
		return bEnabled && ((bContinuous && !fEmitterLifeTime && fCount) || fPulsePeriod || !fParticleLifeTime);
	}
	bool HasEquilibrium() const
	{
		// Effect reaches a steady state: No emitter lifespan or pulsing.
		return bContinuous && fCount && fParticleLifeTime && !fPulsePeriod && !fEmitterLifeTime;
	}
	float GetMaxSpawnDelay() const
	{
		return eSpawnIndirection >= ParticleSpawn_ParentCollide ? fHUGE : fSpawnDelay.GetMaxValue();
	}
	float GetMaxEmitterLife() const
	{
		return GetMaxSpawnDelay() + (bContinuous ? ZeroIsHuge(fEmitterLifeTime.GetMaxValue()) : 0.f);
	}
	float GetMaxParticleLife() const
	{
		return if_pos_else( fParticleLifeTime.GetMaxValue(), fParticleLifeTime.GetMaxValue(), ZeroIsHuge(fEmitterLifeTime.GetMaxValue()) );
	}
	float GetMaxParticleSize() const
	{
		float fMaxSize = fSize.GetMaxValue();
		if (pStatObj)
			fMaxSize *= pStatObj->GetRadius();
		return fMaxSize;
	}
	SPU_NO_INLINE float GetMaxObjectSize( IStatObj* pObj = NULL ) const
	{
		if (pObj)
		{
			AABB bb = pObj->GetAABB();
			if (bNoOffset || ePhysicsType == ParticlePhysics_RigidBody)
				// Rendered at origin.
				return sqrt_fast_tpl( max( bb.min.GetLengthSquared(), bb.max.GetLengthSquared() ) );
			else
				// Rendered at center.
				return bb.GetRadius();
		}
		// Max sprite corner dist.
		else if (bOctagonalShape)
			return 1.083f;
		else
			return 1.415f; 
	}
	float GetMaxVisibleSize( SExternForces const& forces ) const;
	float GetMaxRotationAngle() const
	{
		if (vRotationRate.x != 0.f || vRotationRate.z != 0.f || vRandomRotationRate.x != 0 || vRandomRotationRate.z != 0.f)
			return gf_PI;
		return DEG2RAD( max( abs(vInitAngles.x) + abs(vRandomAngles.x), abs(vInitAngles.z) + abs(vRandomAngles.z) ) );
	}
	AABB GetEmitOffsetBounds() const
	{
		return AABB(vPositionOffset - vRandomOffset, vPositionOffset + vRandomOffset);
	}
	float GetFullTextureArea() const
	{
		// World area corresponding to full texture, thus multiplied by tile count.
		return sqr(2.f * fSize.GetMaxValue()) * float(TextureTiling.nTilesX * TextureTiling.nTilesY);
	}
	SPU_NO_INLINE int GetSubGeometryCount() const
	{
		return pStatObj ? pStatObj->GetSubObjectCount() : 0;
	}
	SPU_NO_INLINE IStatObj::SSubObject* GetSubGeometry(int i) const
	{
		assert(i < GetSubGeometryCount());
		IStatObj::SSubObject* pSub = pStatObj->GetSubObject(i);
		return pSub && pSub->nType==STATIC_SUB_OBJECT_MESH && pSub->pStatObj && pSub->pStatObj->GetRenderMesh() ? pSub : 0;
	}

	void GetMemoryUsage( ICrySizer *pSizer ) const
	{
		pSizer->AddObject(this, sizeof(*this));
		TypeInfo().GetMemoryUsage(pSizer, this);
	}

protected:

	void Init()
	{
		nTexId = -1;
		fTexAspect = 0.f;
		ComputeEnvironmentFlags();
	}
};

struct FEffectLife
{
	OPT_STRUCT(FEffectLife)
	VAR_OPT(bool, bAllChildren)
	VAR_OPT(bool, bIndirectChildren)
	VAR_OPT(bool, bParticleLife)
	VAR_OPT(float, fEmitterMaxLife)
};

/*!	CParticleEffect implements IParticleEffect interface and contain all components necessary to 
		to create the effect
 */
class CParticleEffect : public IParticleEffect, public Cry3DEngineBase, public stl::intrusive_linked_list_node<CParticleEffect>
{
public:
	CParticleEffect();
	CParticleEffect( const char* sName );
	CParticleEffect( const char* sName, const ParticleParams &params );
	~CParticleEffect();

	void Release()
	{
		--m_nRefCounter;
		if (m_nRefCounter <= 0)
			delete this;
	}

	// IParticle interface.
	VIRTUAL IParticleEmitter* Spawn( const PivotLocation& loc, uint32 uEmitterFlags = 0, const SpawnParams* pSpawnParams = NULL );

	VIRTUAL void SetName( const char *sFullName );
	VIRTUAL const char* GetName() const		
		{ return m_strName.c_str(); }
	VIRTUAL string GetFullName() const;

	VIRTUAL void SetEnabled( bool bEnabled );
	VIRTUAL bool IsEnabled() const 
		{ return m_pParticleParams && m_pParticleParams->bEnabled; };

	//////////////////////////////////////////////////////////////////////////
	//! Load resources, required by this particle effect (Textures and geometry).
	VIRTUAL bool LoadResources()
		{ return LoadResources(true); }
	VIRTUAL void UnloadResources()
		{ UnloadResources(true); }

	//////////////////////////////////////////////////////////////////////////
	// Child particle systems.
	//////////////////////////////////////////////////////////////////////////
	int GetChildCount() const
		{ return m_childs.size(); }
	IParticleEffect* GetChild( int index ) const
		{ return &m_childs[index]; }

	VIRTUAL void ClearChilds();
	VIRTUAL void InsertChild( int slot,IParticleEffect *pEffect );
	VIRTUAL int FindChild( IParticleEffect *pEffect ) const;

	VIRTUAL void SetParent( IParticleEffect* pParent );
	VIRTUAL IParticleEffect* GetParent() const 
		{ return m_parent; };

	//////////////////////////////////////////////////////////////////////////
	VIRTUAL void SetParticleParams( const ParticleParams &params );
	VIRTUAL const ParticleParams& GetParticleParams() const 
		{ return GetParams(); };

	void Serialize( XmlNodeRef node, bool bLoading, bool bChildren );
	void GetMemoryUsage( ICrySizer* pSizer ) const;
	
	// Further interface.

	const ResourceParticleParams& GetParams() const;	

	void InstantiateParams()
	{ 
		if (!m_pParticleParams)
			m_pParticleParams = new ResourceParticleParams;
	}

	const char* GetBaseName() const;

	bool IsNull() const
	{
		// Empty placeholder effect.
		return !m_pParticleParams && m_childs.empty();
	}
	bool ResourcesLoaded( bool bAll ) const;
	bool LoadResources( bool bAll ) const;
	void UnloadResources( bool bAll ) const;
	bool IsActive( bool bAll = false ) const;
	void SetEnvironFlags( uint32 nFlags )
	{
		InstantiateParams();
		m_pParticleParams->nEnvFlags |= nFlags;
	}

	CParticleEffect* FindChild( const char* szChildName ) const;

	CParticleEffect* GetIndirectParent() const;
	float GetMaxEffectLife( FEffectLife opts ) const;
	ILINE float GetMaxParticleFullLife() const
	{
		return GetMaxEffectLife( FEffectLife().bParticleLife(1).bIndirectChildren(1) );
	}
	float GetEquilibriumAge( bool bAll ) const;

	void GetEffectCounts( SEffectCounts& counts ) const;

private:

	//////////////////////////////////////////////////////////////////////////
	// Name of effect or sub-effect, minimally qualified.
	// For top-level effects, this includes library.group.
	// For all child effects, is just the base name.
	string													m_strName;					
	ResourceParticleParams*					m_pParticleParams;

	//! Parenting.
	CParticleEffect*								m_parent;
	SmartPtrArray<CParticleEffect>	m_childs;
};





//
// Travel utilities
//

#define fDRAG_APPROX_THRESHOLD	0.01f						// Max inaccuracy we allow in fast drag force approximation

inline float TravelDistance( float fV, float fDrag, float fT )
{
	float fDecay = fDrag*fT;
	if (fDecay < fDRAG_APPROX_THRESHOLD)
		return fV*fT * (1.f - fDecay);
	else
		// Compute accurate distance with drag.
		return fV / fDrag * (1.f - expf(-fDecay));
}

inline float TravelSpeed( float fV, float fDrag, float fT )
{
	float fDecay = fDrag*fT;
	if (fDecay < fDRAG_APPROX_THRESHOLD)
		// Fast approx drag computation.
		return fV * (1.f - fDecay);
	else
		return fV * expf(-fDecay);
}





ILINE

void Travel( Vec3& vPos, Vec3& vVel, float fTime, const SLocalForces& forces )
{
	// Analytically compute new velocity and position, accurate for any time step.
	if (forces.fDrag * fTime >= fDRAG_APPROX_THRESHOLD)
	{
		//
		// Air resistance proportional to velocity is typical for slower laminar movement.
		// For drag d (units in 1/time), wind W, and gravity G:
		//
		//		V' = d (W-V) + G
		//
		//	The analytic solution is:
		//
		//		VT = G/d + W,									terminal velocity
		//		V = (V-VT) e^(-d t) + VT
		//		X = (V-VT) (1 - e^(-d t))/d + VT t
		//
		//	A faster approximation, accurate to 2nd-order t is:
		//
		//		e^(-d t) => 1 - d t + d^2 t^2/2
		//		X += V t + (G + (W-V) d) t^2/2
		//		V += (G + (W-V) d) t
		//

		float fInvDrag = 1.f / forces.fDrag;
		Vec3 vTerm = forces.vWind + forces.vAccel * fInvDrag;
		float fDecay = expf(-forces.fDrag * fTime);
		float fT = (1.0f - fDecay) * fInvDrag;
		vPos += vVel * fT + vTerm * (fTime-fT);
		vVel = vVel * fDecay + vTerm * (1.0f - fDecay);
	}
	else
	{
		// Fast approx drag computation.
		Vec3 vAccel = forces.vAccel + (forces.vWind - vVel) * forces.fDrag;
		vPos += vVel * fTime + vAccel * (fTime*fTime*0.5f);
		vVel += vAccel * fTime;
	}
}

























#endif //__particleeffect_h__
